package com.class05.advice;


/**
 * 注意：
 * 特殊情况 不考虑 访问修饰符和返回值
 * execution(* * ) 这是错误语法
 * execution( *) == 你只要考虑返回值 或者 不考虑访问修饰符 相当于全部不考虑了
 * ○  第四位：指定包的地址
 * 固定的包: com.atguigu.api | service | dao
 * 单层的任意命名: com.atguigu.*  = com.atguigu.api  com.atguigu.dao  * = 任意一层的任意命名
 * 任意层任意命名: com.. = com.atguigu.api.erdaye com.a.a.a.a.a.a.a  ..任意层,任意命名 用在包上!
 * 注意: ..不能用作包开头   public int .. 错误语法  com..
 * 找到任何包下: *..
 * <p>
 * ○  第五位：指定类名称
 * 固定名称: UserService
 * 任意类名: *
 * 部分任意: com..service.impl.*Impl
 * 任意包任意类: *..*
 * <p>
 * ○  第六位：指定方法名称
 * 语法和类名一致
 * 任意访问修饰符,任意类的任意方法: * *..*.*
 * <p>
 * ○  第七位：方法参数
 * 第七位: 方法的参数描述
 * 具体值: (String,int) != (int,String) 没有参数 ()
 * 模糊值: 任意参数 有 或者 没有 (..)  ..任意参数的意识
 * 部分具体和模糊:
 * 第一个参数是字符串的方法 (String..)
 * 最后一个参数是字符串 (..String)
 * 字符串开头,int结尾 (String..int)
 * 包含int类型(..int..)
 * <p>
 * <p>
 * execution(* *..*.*(..))  //任意权限修饰符任意返回值,任意包,任意类,任意方法(任意参数)
 * 不考虑权限修饰符和返回值  只能用一个*
 * // *Impl
 * execution(private int *..*Impl.*(..))
 * <p>
 * <p>
 * execution(public int com.atguigu.service.impl.CalculatorPureImpl.add(int..String))
 * // 指定具体的权限修饰符（public） 具体返回值（int） 具体包（com.atguigu.service.impl.）
 * 具体类(CalculatorPureImpl) 具体方法(int) （必须与int开头，与String结尾，中间任意）
 */

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.Arrays;
import java.util.stream.Stream;

@Aspect   // 表示当前类为切面类
@Component  // 切面类必须被spring管理，所以需要添加@Component注解
public class LogAspect {
//    定义切入点


    //    @Before是前置通知，表示在方法执行之前执行
//    @AfterReturning是返回通知，表示在方法执行之后执行
//    @After是后置通知，表示在方法执行之后执行
//    @Around是环绕通知，表示在方法执行前后都执行,如果该方法有异常，则不会执行，会执行到后置通知
//    @Around环绕通知，相当于上面四个的结合体
//    1、可以通过该通知来获取传入的参数
    @Before("com.class05.point.Class05Pointcut.testClass05Pointcut()")
    public void before(JoinPoint joinpoint) {
        System.out.println("====================================前置通知======================");
//  getSignature()方法可以获取方法的签名信息
        Signature signature = joinpoint.getSignature();
//        getName()方法可以获取方法名称
        System.out.println(signature.getName());
//        getDeclaringType()获取方法声明类型
        Class declaringType = signature.getDeclaringType();
        System.out.println(declaringType);
//        getModifiers()获取方法修饰符 打印的是修饰符对应的值
        int modifiers = signature.getModifiers();
        System.out.println("获取方法修饰符:" + modifiers);
//        getDeclaringTypeName()获取方法声明类型名称
        String declaringTypeName = signature.getDeclaringTypeName();
        System.out.println(declaringTypeName);
        System.out.println("计算开始");
//        获取方法实参
        Object[] args = joinpoint.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
//        获取方法的目标对象
        Object target = joinpoint.getTarget();
        System.out.println(target);
//        获取方法的代理对象
        Object this1 = joinpoint.getThis();
        System.out.println(this1);
        System.out.println("==========================================================");

    }


    //    2、可以通过该通知来获取返回值，但需要定义形参接受返回值并用@AfterReturning注解里面的
//    returning属性来指定形参名称
//    返回通知的输出
    @AfterReturning(value = "com.class05.point.Class05Pointcut.testClass05Pointcut()",
            returning = "result")
    public void afterReturning(JoinPoint joinPoint, int result) {
        System.out.println("=============================返回通知=============================");
        Object[] args = joinPoint.getArgs();
//        数组创建流的方式：
        Arrays.stream(args).forEach(System.out::println);
        System.out.println("结果：" + result);
        System.out.println("==========================================================");
    }
//    3、异常通知，可以使用@AfterThrowing注解里面面面的throwing属性来
//    指定形参名称获取到异常信息,针对JoinPoint获取信息，可以加在任意通知下形参处

    @AfterThrowing(value = "com.class05.point.Class05Pointcut.testClass05Pointcut()"
            , throwing = "throwable")
    public void afterThrowing(JoinPoint joinPoint, Exception throwable) {

        System.out.println("============================异常通知==============================");
        Object[] args = joinPoint.getArgs();
//        数组创建流的方式：
        Arrays.stream(args).forEach(System.out::println);
        System.out.println("打印异常信息的类名：" + throwable.getClass().getName());
        System.out.println("打印异常信息：" + throwable.getMessage());
        Stream.of(throwable.getStackTrace()).forEach(System.out::println);
        System.out.println("==========================================================");
    }

    //    后置通知的输出
    @After("com.class05.point.Class05Pointcut.testClass05Pointcut()")
    public void after() {
        System.out.println("============================后置通知==============================");
        System.out.println("后置通知");
        System.out.println("==========================================================");
    }



//    @Around  = @Before + @After + @AfterReturning + @AfterThrowing
    @Around("com.class05.point.Class05Pointcut.testClass05Pointcut2()")
    public Object around(// 通过在通知方法形参位置声明ProceedingJoinPoint类型的形参，
                         // Spring会将这个类型的对象传给我们
                         ProceedingJoinPoint joinPoint) {
        System.out.println("============================环绕通知==============================");
        Object[] args = null;
        try {
            System.out.println("相当于前置通知");
            args = joinPoint.getArgs();
//            断言，如果参数为空，则抛出异常 阻断程序继续执行
            Assert.notNull(args, "Argument array passed to proceed cannot be null");
            int result = 0;
//            for (Object arg : args) {
//
                int j = 10/0;
//                result /= (int) arg;
//            }

//            proceed()方法可以执行目标方法，并且可以返回目标方法的返回值
            Object proceed = joinPoint.proceed(args);
            result = (int) proceed;
            System.out.println("结果：" + result);


            System.out.println("相当于返回通知");
        } catch (Throwable e) {
            System.out.println("相当于异常通知");
            e.printStackTrace();
        } finally {
            System.out.println("相当于后置通知");
        }

        return args[0];


    }


}
